{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52a1bad2",
   "metadata": {},
   "outputs": [],
   "source": [
    "CSV_FILE = \"/home/ttuser/git/tt-metal/generated/profiler/reports/2025_07_27_19_45_54/ops_perf_results_2025_07_27_19_45_54.csv\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c509cbec",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "from ipywidgets import interact\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b16c5637",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load the CSV file into a pandas DataFrame\n",
    "df = pd.read_csv(CSV_FILE)\n",
    "\n",
    "# Rename the columns for clarity\n",
    "df = df.rename(columns={\n",
    "    'OP CODE': 'Operation',\n",
    "    'HOST DURATION [ns]': 'Host Time',\n",
    "    'OP TO OP LATENCY [ns]': 'Time Between Ops',\n",
    "    'DEVICE FW DURATION [ns]': 'Device Time'\n",
    "})\n",
    "\n",
    "\n",
    "# Filter out rows before compilation finished\n",
    "mask = (df['Operation'] == 'ProfilerNoopOperation') & df['ATTRIBUTES'].str.contains('compilation_finished', na=False)\n",
    "matching_indices = df.index[mask]\n",
    "assert not matching_indices.empty, \"No 'compilation_finished' found in ProfilerNoopOperation attributes\"\n",
    "latest_compilation_flag = matching_indices[-1]\n",
    "df = df.iloc[latest_compilation_flag + 1:]\n",
    "\n",
    "# Find number of training steps\n",
    "mask = (df['Operation'] == 'ProfilerNoopOperation') & df['ATTRIBUTES'].str.contains('iteration_', na=False)\n",
    "matching_indices = df.index[mask]\n",
    "num_training_steps = 3 # for backward compatibility, will be removed before merge\n",
    "if not matching_indices.empty:\n",
    "    filtered_df = df[df.index.isin(matching_indices)]\n",
    "    num_training_steps = len(filtered_df['ATTRIBUTES'].unique())\n",
    "\n",
    "df = df[df['Operation'] != 'ProfilerNoopOperation']\n",
    "\n",
    "all_operations = df['Operation'].unique()\n",
    "\n",
    "num_training_steps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "182749b6",
   "metadata": {},
   "outputs": [],
   "source": [
    "def draw_diagrams_with_aggregation(aggregation):\n",
    "    grouped = df.groupby('Operation').agg({\n",
    "        'Host Time': aggregation,\n",
    "        'Time Between Ops': aggregation,\n",
    "        'Device Time': aggregation\n",
    "    })\n",
    "\n",
    "    time_columns = ['Time Between Ops', 'Device Time', 'Host Time']\n",
    "    topk = 15\n",
    "\n",
    "    for col in time_columns:\n",
    "        # ----- top-k + “Others” slice -------------------------------------\n",
    "        sorted_times = grouped[col].sort_values(ascending=False)\n",
    "        top_times    = sorted_times.head(topk)\n",
    "        others_sum   = sorted_times.iloc[topk:].sum()\n",
    "        if others_sum:\n",
    "            top_times = pd.concat([top_times, pd.Series({'Others': others_sum})])\n",
    "\n",
    "        labels = top_times.index.tolist()\n",
    "        sizes  = top_times.values\n",
    "        pct    = 100 * sizes / sizes.sum()\n",
    "\n",
    "        # ----- plot -------------------------------------------------------\n",
    "        fig, ax = plt.subplots(figsize=(8, 8))\n",
    "        wedges, _ = ax.pie(sizes, startangle=140)   # no autopct ⇒ nothing on pie\n",
    "\n",
    "        legend_text = [f'{lbl} — {p:.1f}%' for lbl, p in zip(labels, pct)]\n",
    "        ax.legend(\n",
    "            wedges,\n",
    "            legend_text,\n",
    "            title=f'Operations (share of {aggregation})',\n",
    "            loc='center left',\n",
    "            bbox_to_anchor=(1, 0.5)\n",
    "        )\n",
    "\n",
    "        ax.set_title(f'Top {topk} Operations by {aggregation} {col}')\n",
    "        ax.axis('equal')\n",
    "        plt.show()\n",
    "\n",
    "        # display(fig)    # ➋ show it once"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8778681",
   "metadata": {},
   "outputs": [],
   "source": [
    "draw_diagrams_with_aggregation('sum')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ade63325",
   "metadata": {},
   "outputs": [],
   "source": [
    "draw_diagrams_with_aggregation('mean')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5809c33b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def name_per_aggregation(aggregation):\n",
    "    if aggregation == 'sum':\n",
    "        return 'Total (per training step)'\n",
    "    elif aggregation == 'mean':\n",
    "        return 'Average'\n",
    "    else:\n",
    "        raise ValueError(f\"Unsupported aggregation: {aggregation}\")\n",
    "    \n",
    "def draw_charts_with_aggregation(aggregation):\n",
    "    grouped = df.groupby('Operation').agg({\n",
    "        'Host Time': aggregation,\n",
    "        'Time Between Ops': aggregation,\n",
    "        'Device Time': aggregation\n",
    "    })\n",
    "\n",
    "    time_columns = ['Time Between Ops', 'Device Time', 'Host Time']\n",
    "\n",
    "    # Loop through each time column to create horizontal bar charts and pie charts\n",
    "    for col in time_columns:\n",
    "        # Extract the total times per operation for the current column\n",
    "        total_times = grouped[col] / 1_000_000  # Convert from nanoseconds to milliseconds\n",
    "        if aggregation == 'sum':\n",
    "            total_times /= num_training_steps  # Normalize by number of training steps if aggregation is 'sum'\n",
    "        \n",
    "        # Create a horizontal bar chart\n",
    "        plt.figure(figsize=(10, 6))\n",
    "        total_times.sort_values().plot(kind='barh', color='skyblue') \n",
    "        plt.title(f'{name_per_aggregation(aggregation)} {col} per Operation')\n",
    "        plt.xlabel(f'{name_per_aggregation(aggregation)} {col} (ms)')\n",
    "        plt.ylabel('Operation')\n",
    "        plt.tight_layout()\n",
    "        plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "61b59e2a",
   "metadata": {},
   "outputs": [],
   "source": [
    "draw_charts_with_aggregation('mean')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b545afab-bcb4-4abd-b88e-bfeebebb87df",
   "metadata": {},
   "outputs": [],
   "source": [
    "draw_charts_with_aggregation('sum')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a6d7008b",
   "metadata": {},
   "outputs": [],
   "source": [
    "@interact(operation=all_operations)\n",
    "def draw_per_operation_stats(operation):\n",
    "    df_op = df[df['Operation'] == operation]\n",
    "    if df_op.empty:\n",
    "        print(f\"No data available for operation: {operation}\")\n",
    "        return\n",
    "\n",
    "    metrics = [\n",
    "        ('Host Time (ms)',          df_op['Host Time']        / 1_000_000),\n",
    "        ('Time Between Ops (ms)',   df_op['Time Between Ops'] / 1_000_000),\n",
    "        ('Device Time (ms)',        df_op['Device Time']      / 1_000_000),\n",
    "    ]\n",
    "\n",
    "    for title, series in metrics:\n",
    "        # --- build figure explicitly so we know which one to close ------------\n",
    "        fig, ax = plt.subplots(figsize=(10, 6))\n",
    "        ax.plot(series.index, series.values, marker='o', color='red')\n",
    "        ax.set_title(f'{operation} – {title}')\n",
    "        ax.set_xlabel('Index')\n",
    "        ax.set_ylabel(title)\n",
    "        ax.grid(True)\n",
    "        fig.tight_layout()\n",
    "        plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1f501d43",
   "metadata": {},
   "outputs": [],
   "source": [
    "# example how to manually extract performance data for a specific operation\n",
    "e = df[df['Operation'] == 'Untilize']\n",
    "e = e[['Operation', 'Host Time', 'Time Between Ops', 'Device Time']]\n",
    "e"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52405bc7",
   "metadata": {},
   "outputs": [],
   "source": [
    "metrics = ['Host Time', 'Time Between Ops', 'Device Time']\n",
    "\n",
    "def anomaly_detection_per_operation(operation, metric):\n",
    "    df_op = df[df['Operation'] == operation]\n",
    "    if df_op.empty:\n",
    "        print(f\"No data available for operation: {operation}\")\n",
    "        return\n",
    "\n",
    "    # Calculate the mean and standard deviation for each metric\n",
    "    \n",
    "    series = df_op[metric]\n",
    "    \n",
    "    mean = series.mean()\n",
    "    std_dev = series.std()\n",
    "    \n",
    "    # Identify anomalies as points that are more than 3 standard deviations from the mean\n",
    "    anomalies = series[(series < mean - 3 * std_dev) | (series > mean + 3 * std_dev)]\n",
    "    \n",
    "    if not anomalies.empty:\n",
    "        return df_op.loc[anomalies.index]\n",
    "    return None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9cb0550e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "def is_not_nan(value):\n",
    "    \"\"\"Check if a value is not NaN.\"\"\"\n",
    "    return pd.notna(value) and value != 'NaN' and value != 'nan' and value != ''\n",
    "\n",
    "@interact(operation=all_operations, metric_name=metrics)\n",
    "def show_anomalies_attributes_per_metric(operation, metric_name):\n",
    "    anomaly_df = anomaly_detection_per_operation(operation, metric_name)\n",
    "\n",
    "    if anomaly_df is None:\n",
    "        print(f\"No anomalies detected for {operation} in {metric_name}.\")\n",
    "        return\n",
    "\n",
    "    for index, row in anomaly_df.iterrows():\n",
    "        attr = row['ATTRIBUTES']\n",
    "        core_count = row['CORE COUNT']\n",
    "        metric_value = row[metric_name]\n",
    "\n",
    "        # find all columns with prefix `INPUT_`\n",
    "        input_columns = [col for col in row.index if col.startswith('INPUT_')]\n",
    "        input_values = {col: row[col] for col in input_columns if is_not_nan(row[col])}\n",
    "\n",
    "        # improve print of dictionary with json\n",
    "        input_values = json.dumps(input_values, indent=8)\n",
    "\n",
    "        if isinstance(attr, str):\n",
    "            attr = attr.replace(';', ',')\n",
    "            attr = attr.replace('\\'', '\"')\n",
    "            try:\n",
    "                attr = json.loads(attr)\n",
    "            except:\n",
    "                pass\n",
    "            attr = json.dumps(attr, indent=8) if isinstance(attr, dict) else attr\n",
    "\n",
    "        print(f\"Anomaly at index {index}: \")\n",
    "        print(f\"    {metric_name} = {metric_value / 1_000_000} ms\")\n",
    "        print(f\"    core count = {core_count}\")\n",
    "        print(f\"    inputs = {input_values}\")\n",
    "        print(f\"    attributes = {attr}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6da4c9cc",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10b7fc2d",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
